/**
 * Copyright 2020 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.dbfly.mybatis.util;

import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.Arrays;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.ibatis.session.Configuration;
import org.w3c.dom.Document;

import com.jianggujin.dbfly.mybatis.annotation.JTransient;
import com.jianggujin.dbfly.util.JDBFlyException;
import com.jianggujin.dbfly.util.JStringUtils;
import com.jianggujin.dbfly.util.JTypeParameterUtils;

/**
 * Mybatis辅助工具
 * 
 * @author jianggujin
 *
 */
public class JMybatisUtils {
    private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY;
    private static final TransformerFactory TRANSFORMER_FACTORY;
    static {
        DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
        DOCUMENT_BUILDER_FACTORY.setXIncludeAware(false);
        DOCUMENT_BUILDER_FACTORY.setExpandEntityReferences(false);

        TRANSFORMER_FACTORY = TransformerFactory.newInstance();
        TRANSFORMER_FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        TRANSFORMER_FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
    }

    /**
     * 通过类和方法获取msId
     * 
     * @param type
     * @param method
     * @return
     */
    public static String getMsId(Class<?> type, Method method) {
        return JStringUtils.format("{}.{}", type.getName(), method.getName());
    }

    /**
     * 过滤未解析的Method，使用{@link JTransient}注解的方法也将被忽略
     * 
     * @param configuration
     * @param type
     * @return
     */
    public static Method[] filterUnParseMethods(Configuration configuration, Class<?> type) {
        return Arrays.stream(type.getMethods()).filter(method -> !configuration.hasStatement(getMsId(type, method))
                && !method.isAnnotationPresent(JTransient.class)).toArray(Method[]::new);
    }

    /**
     * 获取Mapper的泛型实体类
     * 
     * @param mapperClass
     * @param declaringClass
     * @return
     */
    public static Class<?> getEntityClass(Class<?> mapperClass, Class<?> declaringClass) {
        Class<?> entityClass = null;
        try {
            entityClass = JTypeParameterUtils.findTypeParameter(mapperClass, declaringClass, 0);
        } catch (Exception e) {
            throw new JDBFlyException(JStringUtils.format("无法获取{}中方法的泛型信息!", declaringClass.getCanonicalName()), e);
        }
        if (Object.class.equals(entityClass) || entityClass == null) {
            throw new JDBFlyException("无法获取{}中方法的泛型信息!", declaringClass.getCanonicalName());
        }
        return entityClass;
    }

    public static Document newDocument() throws ParserConfigurationException {
        Document document = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder().newDocument();
        document.setXmlStandalone(true);
        return document;
    }

    public static void transform(Document document, OutputStream out) throws TransformerException {
        Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
        transformer.setOutputProperty(OutputKeys.VERSION, "1.0");
        transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//mybatis.org//DTD Mapper 3.0//EN");
        transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://mybatis.org/dtd/mybatis-3-mapper.dtd");
        transformer.transform(new DOMSource(document), new StreamResult(out));
    }
}
